
#include "MMDIoTSDK.h"
#include <PubSubClient.h>
#include <SHA256.h>

//连接检查间隔
#define CHECK_INTERVAL 10000
//Buffer发送间隔
#define BUFFER_SEND_INTERVAL 100000
//Buffer最大长度
#define MESSAGE_BUFFER_SIZE 20

static const char *mqttServer = NULL;
static const char *deviceName = NULL;
static const char *productKey = NULL;
static const char *deviceSecret = NULL;
static const char *region = NULL;

struct DeviceProperty
{
    String key;
    String value;
};

DeviceProperty PropertyMessageBuffer[MESSAGE_BUFFER_SIZE];

#define MQTT_PORT 1883

#define SHA256HMAC_SIZE 32
#define DATA_CALLBACK_SIZE 20

#define SUB_QOS 1

static unsigned long lastMs = 0;

static PubSubClient *client = NULL;

char MMDIoTSDK::clientId[256] = "";
char MMDIoTSDK::mqttUsername[100] = "";
char MMDIoTSDK::mqttPwd[256] = "";
char MMDIoTSDK::domain[150] = "";

char MMDIoTSDK::TOPIC_PROP_POST[150] = "";
char MMDIoTSDK::TOPIC_PROP_SET[150] = "";
char MMDIoTSDK::TOPIC_EVENT[150] = "";

static String hmac256(const String &signcontent, const String &ds)
{
    byte hashCode[SHA256HMAC_SIZE];
    SHA256 sha256;

    const char *key = ds.c_str();
    size_t keySize = ds.length();

    sha256.resetHMAC(key, keySize);
    sha256.update((const byte *)signcontent.c_str(), signcontent.length());
    sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode));

    String sign = "";
    for (byte i = 0; i < SHA256HMAC_SIZE; ++i)
    {
        sign += "0123456789ABCDEF"[hashCode[i] >> 4];
        sign += "0123456789ABCDEF"[hashCode[i] & 0xf];
    }

    return sign;
}

static void parmPass(JsonVariant parm)
{
    for (int i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (poniter_array[i].key)
        {
            Serial.print("poniter_array[i].key [");
            Serial.print(String(poniter_array[i].key));
            Serial.println("] ");
            bool hasKey = parm.containsKey(poniter_array[i].key);
            if (hasKey)
            {
                poniter_array[i].fp(parm);
            }
        }
    }
}
// 所有云服务的回调都会首先进入这里，例如属性下发
static void callback(char *topic, byte *payload, unsigned int length)
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    payload[length] = '\0';
    Serial.println((char *)payload);

    if (strstr(topic, MMDIoTSDK::TOPIC_PROP_SET))
    {

        StaticJsonDocument<200> doc;
        DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据

        if (!error) //检查反序列化是否成功
        {
            parmPass(doc.as<JsonVariant>()); //将参数传递后打印输出
        }
    }
}

void MMDIoTSDK::mqttCheckConnect()
{
    if (!client->connected())
    {
        Serial.print("Attempting MQTT connection...");
        client->disconnect();
        // Attempt to connect
        if (client->connect(clientId, mqttUsername, mqttPwd))
        {
            Serial.println("MQTT Connected!");
            //订阅
            client->subscribe(TOPIC_PROP_SET, SUB_QOS);
        }
        else
        {
            Serial.print("MQTT Connect err:");
            Serial.println(client->state());
        }
    }
    else
    {
        Serial.println("state is connected");
    }
}

void MMDIoTSDK::begin(Client &espClient,
                         const char *_mqttServer,
                         const char *_productKey,
                         const char *_deviceName,
                         const char *_deviceSecret)
{

    client = new PubSubClient(espClient);
    mqttServer = _mqttServer;
    productKey = _productKey;
    deviceName = _deviceName;
    deviceSecret = _deviceSecret;
    long times = millis();
    String timestamp = String(times);

    sprintf(clientId, "%s|securemode=3,signmethod=hmacsha256,timestamp=%s|", deviceName, timestamp.c_str());

    String signcontent = "clientId";
    signcontent += deviceName;
    signcontent += "deviceName";
    signcontent += deviceName;
    signcontent += "productKey";
    signcontent += productKey;
    signcontent += "timestamp";
    signcontent += timestamp;

    String pwd = hmac256(signcontent, deviceSecret);

    strcpy(mqttPwd, pwd.c_str());

    sprintf(mqttUsername, "%s&%s", deviceName, productKey);
    sprintf(TOPIC_PROP_POST, "thing/%s/%s/event/property/post", productKey, deviceName);
    sprintf(TOPIC_PROP_SET, "thing/%s/%s/service/property/set", productKey, deviceName);
    sprintf(TOPIC_EVENT, "thing/%s/%s/event", productKey, deviceName);

    client->setServer(mqttServer, MQTT_PORT); /* 连接WiFi之后，连接MQTT服务器 */
    client->setCallback(callback);

    mqttCheckConnect();
}

void MMDIoTSDK::loop()
{
    client->loop();
    if (millis() - lastMs >= CHECK_INTERVAL)
    {
        lastMs = millis();
        //定时检查连接状态
        mqttCheckConnect();
        //定时发送设备属性
        messageBufferCheck();
    }
}

unsigned long lastSendMS = 0;

// 检查是否有数据需要发送
void MMDIoTSDK::messageBufferCheck()
{
    
    unsigned long nowMS = millis();
    // 100s 发送一次数据
    if (nowMS - lastSendMS > BUFFER_SEND_INTERVAL)
    {
        sendBuffer();
        lastSendMS = nowMS;
    }

}

void addMessageToBuffer(char *key, String value)
{
    int i;
    bool isExist = false;
    for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {   
        //value更新
        if (PropertyMessageBuffer[i].key == key) {
            PropertyMessageBuffer[i].value = value;
            isExist = true;
            break;
        }
    }
    if (!isExist) {
        //新增
        for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
        {
            if (PropertyMessageBuffer[i].key.length() == 0)
            {
                PropertyMessageBuffer[i].key = key;
                PropertyMessageBuffer[i].value = value;
                break;
            }
        }
    }
}

 void removeMessageToBuffer(char *key)
{
    int i;
    for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {   
        if (PropertyMessageBuffer[i].key == key) {
            PropertyMessageBuffer[i].key = "";
            PropertyMessageBuffer[i].value = "";
            break;
        }
    }
}

void MMDIoTSDK::send(const char *msg)
{
    Serial.print("send ");
    Serial.print(msg);
    boolean d = client->publish(TOPIC_PROP_POST, msg);
    Serial.print(" result [ 1 成功、0 失败]:");
    Serial.println(d);
}

// 发送 buffer 数据
void MMDIoTSDK::sendBuffer()
{
    int i;
    String buffer;
    for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {
        if (PropertyMessageBuffer[i].key.length() > 0)
        {
            buffer += "\"" + PropertyMessageBuffer[i].key + "\":" + PropertyMessageBuffer[i].value + ",";
        }
    }

    buffer = "{" + buffer.substring(0, buffer.length() - 1) + "}";
    send(buffer.c_str());
}

void MMDIoTSDK::addBuffer(char *key, float number)
{
    addMessageToBuffer(key, String(number));
}

void MMDIoTSDK::addBuffer(char *key, int number)
{
    addMessageToBuffer(key, String(number));
}

void MMDIoTSDK::addBuffer(char *key, double number)
{
    addMessageToBuffer(key, String(number));
}

void MMDIoTSDK::addBuffer(char *key, char *text)
{
    addMessageToBuffer(key, "\"" + String(text) + "\"");
}

void MMDIoTSDK::removeBuffer(char *key)
{
    removeMessageToBuffer(key);
}

int MMDIoTSDK::bindData(char *key, poniter_fun fp)
{
    int i;
    for (i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (!poniter_array[i].fp)
        {
            poniter_array[i].key = key;
            poniter_array[i].fp = fp;
            return 0;
        }
    }
    return -1;
}

int MMDIoTSDK::unbindData(char *key)
{
    int i;
    for (i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (!strcmp(poniter_array[i].key, key))
        {
            poniter_array[i].key = NULL;
            poniter_array[i].fp = NULL;
            return 0;
        }
    }
    return -1;
}

// --new--

boolean MMDIoTSDK::publish(const char *topic, const char *payload)
{
    return client->publish(topic, payload);
}

boolean MMDIoTSDK::subscribe(char *topic, uint8_t qos)
{
    boolean ret = false;
    if (client->subscribe(topic, qos))
    {
        ret = true;
        Serial.print("subcribe: ");
        Serial.println(topic);
    }
    return ret;
}

boolean MMDIoTSDK::subscribe(char *topic, poniter_fun fp)
{
    return subscribe(topic, 1, fp);
}

boolean MMDIoTSDK::subscribe(char *topic, uint8_t qos, poniter_fun fp)
{
    boolean ret = false;
    if (client->subscribe(topic, qos))
    {
        ret = true;
        bindData(topic, fp);
        Serial.print("subcribe: ");
        Serial.println(topic);
    }
    return ret;
}

boolean MMDIoTSDK::unsubscribe(char *topic)
{
    boolean ret = false;
    if (client->unsubscribe(topic))
    {
        ret = true;
        unbindData(topic);
        Serial.print("unsubcribe: ");
        Serial.println(topic);
    }
    return ret;
}

